<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN">
<!-- Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) -->
<html>
<head>
<title>Drag &amp; Drop Sortable Lists with JavaScript and CSS</title>
<link rel="stylesheet" type="text/css" href="common/common.css"/>
<link rel="stylesheet" type="text/css" href="common/lists.css"/>
<style type="text/css"><!--
	.statusbox {
		font-size: 13px;
		font-family: Monaco, monospace;
		width: 15em;
	}
	ul.boxy li { margin: 0px; }
	#phonetics td {
		margin: 0px;
		padding: 0px 1em;
		vertical-align: top;
		width: 100px;
	}
	#phonetic1 li, #phonetic2 li, #phonetic3 li { margin: 0px; }
	#phonetic2 li {
		margin-bottom: 4px;
	}
	#phonetic3 { margin-top: -4px; }
	#phonetic3 li { margin-top: 4px; }
	#phoneticlong {
		margin-bottom: 1em;
	}
	#phoneticlong li, #buttons li {
		margin-bottom: 0px;
		margin-top: 4px;
	}

	#boxes {
		font-family: Arial, sans-serif;
		list-style-type: none;
		margin: 0px;
		padding: 0px;
		width: 300px;
	}
	#boxes li {
		cursor: move;
		position: relative;
		float: left;
		margin: 2px 2px 0px 0px;
		width: 33px;
		height: 28px;
		border: 1px solid #000;
		text-align: center;
		padding-top: 5px;
		background-color: #eeeeff;
	}

	#twolists td {
		width: 300px;
		vertical-align: top;
	}
	#twolists1 li {
		font-family: sans-serif;
	}
	#twolists2 {
		border: 1px dashed #fff;
	}
	#twolists2 li {
		font-family: serif;
		background-color: #eedddd;
	}
	.inspector {
		font-size: 11px;
	}
	//-->
</style>

<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/core.js"></script>
<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/events.js"></script>
<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/css.js"></script>
<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/coordinates.js"></script>
<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/drag.js"></script>
<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/dragsort.js"></script>
<script language="JavaScript" type="text/javascript" src="../source/org/tool-man/cookies.js"></script>

<script language="JavaScript" type="text/javascript"><!--
	var dragsort = ToolMan.dragsort()
	var junkdrawer = ToolMan.junkdrawer()

	window.onload = function() {
		junkdrawer.restoreListOrder("numeric")
		junkdrawer.restoreListOrder("phonetic1")
		junkdrawer.restoreListOrder("phonetic2")
		junkdrawer.restoreListOrder("phonetic3")
		junkdrawer.restoreListOrder("phoneticlong")
		junkdrawer.restoreListOrder("boxes")
		junkdrawer.restoreListOrder("buttons")
		//junkdrawer.restoreListOrder("twolists1")
		//junkdrawer.restoreListOrder("twolists2")

		dragsort.makeListSortable(document.getElementById("numeric"),
				verticalOnly, saveOrder)

		dragsort.makeListSortable(document.getElementById("phonetic1"),
				verticalOnly, saveOrder)
		dragsort.makeListSortable(document.getElementById("phonetic2"),
				verticalOnly, saveOrder)
		dragsort.makeListSortable(document.getElementById("phonetic3"),
				verticalOnly, saveOrder)
		dragsort.makeListSortable(document.getElementById("phoneticlong"),
				verticalOnly, saveOrder)

		dragsort.makeListSortable(document.getElementById("boxes"),
				saveOrder)

		dragsort.makeListSortable(document.getElementById("buttons"),
				saveOrder)

		/*
		dragsort.makeListSortable(document.getElementById("twolists1"),
				saveOrder)
		dragsort.makeListSortable(document.getElementById("twolists2"),
				saveOrder)
		*/
	}

	function verticalOnly(item) {
		item.toolManDragGroup.verticalOnly()
	}

	function speak(id, what) {
		var element = document.getElementById(id);
		element.innerHTML = 'Clicked ' + what;
	}

	function saveOrder(item) {
		var group = item.toolManDragGroup
		var list = group.element.parentNode
		var id = list.getAttribute("id")
		if (id == null) return
		group.register('dragend', function() {
			ToolMan.cookies().set("list-" + id, 
					junkdrawer.serializeList(list), 365)
		})
	}

	//-->
</script>
</head>
<body>

<h1>Drag &amp; Drop Sortable Lists with JavaScript and CSS</h1>
<ul class="breadcrumb">
	<li class="first"><a href="http://tool-man.org/">Home</a></li>
	<li><a href="./index.html">Other Examples</a></li>
</ul>
<br class="clear"/>

<div class="sidebar">
	<p><b>Download</b></p>
	
	<p><a href="ToolManDHTML-0.2.zip">Version 0.2</a>&nbsp;&nbsp;(<a href="LICENSE.txt">license</a>)</p>
</div>

<div class="sidebar">
<p><b>Update 4/26</b>: tested and working as expected in IE6, Firefox 1.0,
and Safari 1.3, unless otherwise noted.</p>
</div>

<p>In Web applications I've seen numerous &#8212; and personally implemented 
a few &#8212; ways to rearrange items in a list.  All of those were
indirect interactions typically involving something like up/down arrows next
to each item.  The most heinous require server roundtrips for each 
modification...boo.</p>

<p>Then I came across Simon Cozens' example
of <a href="http://blog.simon-cozens.org/6785.html">rearranging a list via 
drag &amp; drop</a>.  I was so inspired I had to try it out myself.</p>

<h2>Example: A Basic List</h2>

<p>Essentially 
<a href="http://blog.simon-cozens.org/6785.html">Simon Cozen's example</a>
with some subtle enhancements.  It's not obvious, but go ahead and
<b>rearrange the items</b>:</p>

<ul id="numeric">
	<li itemID="1">one</li>
	<li itemID="2">two</li>
	<li itemID="3">three</li>
</ul>

<div class="sidebar">
<p>Click an <input class="inspector" type="button" value="Inspect" onclick="junkdrawer.inspectListOrder('numeric')" style="margin: 0px; padding: 0px; font-size: 11px;"/>
button to reveal the serialized version of the associated list.</p>
</div>

<p>In Firefox you can also drag the <i>bullet</i> to move an item.  Keen.</p>

<p><b>Saving the reorderd list</b> is possible by inspecting the DOM.  All
the sortable lists on this page retain their order via cookies (try 
rearranging a list and then reloading the page).  Read a
<a href="http://blog.tool-man.org/saving-a-reordered-list/14">description of the technique</a> on my blog.
</p>

<h2>Example: Add Some Style</h2>

<div class="sidebar">
<p>Firefox exhibits a quirky bug on this example.  If the list items
have a bottom margin, all following content pops down a few pixels when
you start dragging the item.  No such problem when the item has a top
margin so a workaround is possible.</p>
</div>

<p>I added some styling and cursor hinting in an attempt to make
the dragability more obvious (see 
<a href="edit-in-place.html">in-place editing</a> for an example
with drag handles)</a>.</p>

<table id="phonetics">
	<tr>
		<td>
			<ul id="phonetic1" class="boxy">
				<li>alpha</li>
				<li>bravo</li>
				<li>charlie</li>
			</ul>
		</td>
		<td>
			<ul id="phonetic2" class="boxy">
				<li>alpha</li>
				<li>bravo</li>
				<li>charlie</li>
			</ul>
		</td>
		<td>
			<ul id="phonetic3" class="boxy">
				<li>alpha</li>
				<li>bravo</li>
				<li>charlie</li>
			</ul>
		</td>
	</tr>
	<tr>
		<td class="caption">no margin on list items</td>
		<td class="caption">4px bottom margin on list items.  Firefox exhibits 
		bug when dragging.</td>
		<td class="caption">Firefox workaround: -4px top margin on list, 4px
		top margin on list items.</td>
	</tr>
</table>

<p style="margin-top: 2em">This next list is intentionally long to see how well the technique 
scales up and uncover other interaction issues.</p>

<!-- yeah, it's invalid XHTML.  Suck it up, pedant-boy :) -->
<ul id="phoneticlong" class="boxy">
	<li itemID="a">alpha</li>
	<li itemID="b">bravo</li>
	<li itemID="c">charlie</li>
	<li itemID="d">delta</li>
	<li itemID="e">echo</li>
	<li itemID="f">foxtrot</li>
	<li itemID="g">golf</li>
	<li itemID="h">hotel</li>
	<li itemID="i">india</li>
	<li itemID="j">juliet</li>
	<li itemID="k">kilo</li>
	<li itemID="l">lima</li>
	<li itemID="m">mike</li>
	<li itemID="n">november</li>
	<li itemID="o">oscar</li>
	<li itemID="p">papa</li>
	<li itemID="q">quebec</li>
	<li itemID="r">romeo</li>
	<li itemID="s">sierra</li>
	<li itemID="t">tango</li>
	<li itemID="u">uniform</li>
	<li itemID="v">victor</li>
	<li itemID="w">whiskey</li>
	<li itemID="x">xray</li>
	<li itemID="y">yankee</li>
	<li itemID="z">zulu</li>
</ul>

<p><input class="inspector" type="button" value="Inspect" onclick="junkdrawer.inspectListOrder('phoneticlong')"/></p>

<p>You'll notice if part of this list is below the fold, it requires
at least 2 drags to move an item from the beginning to the end (technically
3 drags if you count the one on your browser's scrollbar).  Automatic 
scrolling, like in Word or Excel, is a well established solution to this 
problem.  Adding that is a work in progress.</p>


<h2>Example: Sorting in two dimensions</h2>

<div class="sidebar">
<p>Yet another bizarre bug in firefox.  The short version:
use a DIV instead of a BR to clear the <code>float: left</code>
else things won&#8217;t remain as you expect them once you start 
dragging.</p>
</div>

<p>With sorting vertically oriented items under our belt, onto
the next challenge: sorting floated, wrapped list items.  Earlier 
versions of my code had separate scripts for vertical, horizontal, and 
wrapped lists.  Now they are unified into one script that <i>does it all</i>.
Amazing!</p>

<ul id="boxes">
	<li class="box">A</li>
	<li class="box">B</li>
	<li class="box">C</li>
	<li class="box">D</li>
	<li class="box">E</li>
	<li class="box">F</li>
	<li class="box">G</li>
	<li class="box">H</li>
	<li class="box">I</li>
	<li class="box">J</li>
	<li class="box">K</li>
	<li class="box">L</li>
	<li class="box">M</li>
	<li class="box">N</li>
	<li class="box">O</li>
	<li class="box">P</li>
	<li class="box">Q</li>
	<li class="box">R</li>
	<li class="box">S</li>
	<li class="box">T</li>
	<li class="box">U</li>
	<li class="box">V</li>
	<li class="box">W</li>
	<li class="box">X</li>
	<li class="box">Y</li>
	<li class="box">Z</li>
</ul>

<div style="clear: left;"><br/></div>

<p><input class="inspector" type="button" value="Inspect" onclick="junkdrawer.inspectListOrder('boxes')"/></p>

<p>A previous version determined when to swap based on the position of the
top-left corner of the item being dragged.  It was this example and the 
<a href="edit-in-place.html#slideshow-example">slide arranger</a>
example which illustrated that the better interaction is to base this on
the position of the cursor, which is how the script works now.</p>

<h2>Example: Sortable links or buttons</h2>

<div class="sidebar">
<p>Firefox and Safari still generate a 'click' event even after performing
a drag.  This can be overcome with more JavaScript.</p>
</div>

<div class="sidebar">
<p>IE isn't rendering the link elements with <code>display: block</code>.
So only the link text is clickable.</p>
</div>

<p>Sortable items containing links.  Links are 'display: block' so the entire
item is clickable (except this isn't so in IE).  As buttons go these aren't very good; they lack
button affordances and behavior.  But I think you get the general idea.</p>

<div id="buttonsStatus" class="statusbox" style="position: relative; left: 11em">&nbsp;</div>
<ul id="buttons" class="sortable boxy clickable">
	<li itemID="Save"><a href="#" onclick="speak('buttonsStatus', this.innerHTML); return false;">Save</a></li>
	<li itemID="Cancel"><a href="#" onclick="speak('buttonsStatus', this.innerHTML); return false;">Cancel</a></li>
	<li itemID="Preview"><a href="#" onclick="speak('buttonsStatus', this.innerHTML); return false;">Preview</a></li>
	<li itemID="Print"><a href="#" onclick="speak('buttonsStatus', this.innerHTML); return false;">Print</a></li>
</ul>
<br/>

<p>Very few people keep the mouse perfectly still when clicking on
something.  To avoid accidental drags these buttons have a drag 
threshold, as do all the examples on this page.</p>


<h2>Additional examples</h2>

<ul>
	<li><p>The editable lists and presentation slide arranger examples on 
	<a href="edit-in-place.html">in-place editing</a> demonstrate closer to 
	"real world" usage of this technique.</p></li>

	<li><p>These <a href="toolbars.html">toolbar examples</a>
	combine sortable buttons and hard core CSS styling to create
	Windows-like toolbars.</p></li>
</ul>
	
<h2>TODO: Example: Drag between two lists</h2>

<p><a href="http://neb.net/nlog/2005/04/14/what-a-drop/">Benjamin Levy</a>
is credited with the first version (that was based on my code) of
<a href="http://blog.tool-man.org/drag-between-lists/12">dragging between 
two lists</a>.  Eventually this will be supported in the ToolMan DHTML Library.
<!--I've shamelessly (and with Benjamin's permission) folded
his example into my latest version.--></p>

<!--
<p><b>Note: this doesn't work yet.</b></p>

<table id="twolists">
	<tr>
		<td>
			<ul id="twolists1" class="boxy">
				<li>alpha</li>
				<li>bravo</li>
				<li>charlie</li>
			</ul>
		</td>
		<td>
			<ul id="twolists2" class="boxy">
				<li>one</li>
				<li>two</li>
				<li>three</li>
			</ul>
		</td>
	</tr>
</table>

<p>Desired interactions:</p>

<ol>
<li><p>Two distinct drag modes. One for sorting and another for dragging &amp; d ropping.</p></li>
<li><p>You&#8217;re only ever in one mode per drag operation</p></li>
<li><p>The direction you drag when starting the drag operation determines the dragging mode. Dragging up or down triggers sorting. Drag left or right triggers drag &amp; drop.  This differs from Benjamin&#8217;s example.  It&#8217;s based on an interaction described by Alan Cooper in &#8220;About Face 2.0&#8221;.</p></li>
<li><p>Easier to initiate a sort than to start drag &amp; drop. Weight the drag threshold in favor of a sort by making the sort threshold smaller than the DnD threshold.</p></li>
</ol>

<p>This will require these new features:</p>

<ul>
<li><p>create a drag &amp; drop library with sources and targets (or at least targets)</p></li>
<li><p>change the current drag threshold from a simple numeric distance to a function or object (similar to how contraints are accomplished)</p></li>
</ul>

<p>Fun!</p>
-->

<h2>TODO: Example: Sorting within nested lists</h2>

<p>Steps:</p>
<ol>
<li>Complete &#8220;Drag between two lists&#8221;</li>
<li>???</li>
<li>Profit</li>
</ol>

<h2>TODO: sortable table cells or cell contents</h2>

<p>This will take more work than I initially thought.  That's because the
sorting script currently makes the assumption that sortable elements are
immediate siblings of the same parent element.</p>

<p>I may start by cloning the existing dragsort.js and customizing it for
table cells, then see if the two can be generalized without making either
a pain to use.</p>

<h2>TODO</h2>

<ul>
	<li><p>FIXME: given a list of items with no margin, grab the
	very top edge of an item to drag, drag it to the bottom, keep moving
	your mouse.  The item will keep swapping with its previous 
	sibling.</p></li>

	<li><p>FIXME: Firefox, ignore the click after a drag</p></li>
</ul>

<ul class="breadcrumb">
	<li class="first"><a href="http://tool-man.org/">Home</a></li>
	<li><a href="./index.html">Other Examples</a></li>
</ul>

<div id="copyright">Copyright &copy; 2005 Tim Taylor Consulting
(<a href="LICENSE.txt">license</a>)</div>

</body>

</html>
